// $Id$
// Licensed under Apache License Version 2.0
// See: http://www.apache.org/licenses/LICENSE-2.0.html
package de.servicezombie.tcmdb.rest.service;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.cxf.helpers.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.util.Assert;

import de.servicezombie.cxf.JaxbUnmarshallerFascade;
import de.servicezombie.cxf.WebserviceTemplate;
import de.servicezombie.cxf.WebserviceTemplateFactory;
import de.servicezombie.lang.Configuration;
import de.servicezombie.tcmdb.TcmdbRecordNotFoundException;
import de.servicezombie.tcmdb.TcmdbValidationFailedException;
import de.servicezombie.tcmdb.dao.mongo.TestExecutionDao;
import de.servicezombie.tcmdb.dao.mongo.TestcaseDefinitionDao;
import de.servicezombie.tcmdb.rest.RestTransformerSupport;
import de.servicezombie.tcmdb.rest.SameInstanceTransformer;
import de.servicezombie.tcmdb.rest.vo.WsListResult;
import de.servicezombie.tcmdb.rest.vo.WsTestExecution;
import de.servicezombie.tcmdb.rest.vo.WsTestcaseDefinition;
import de.servicezombie.tcmdb.service.RolloutDefinitionByExistingExecutionStrategy;
import de.servicezombie.tcmdb.service.TestExecutionManipulationService;
import de.servicezombie.tcmdb.util.TestExecutionUtils;
import de.servicezombie.tcmdb.util.validations.ValidateTestExecutionBeforeSave;

public class TestExecutionServiceRestImpl implements TestExecutionService {

	private static final Logger log = LoggerFactory.getLogger(TestExecutionServiceRestImpl.class);

	private ValidateTestExecutionBeforeSave validateTestExecutionBeforeSave;
	private TestExecutionManipulationService testExecutionManipulationService;
	private TestExecutionDao testExecutionDao;
	private TestcaseDefinitionDao testcaseDefinitionDao;
	private JaxbUnmarshallerFascade unmarshallerFascade;
	private Configuration configuration;
	
	public void setValidateTestExecutionBeforeSave(ValidateTestExecutionBeforeSave validateTestExecutionBeforeSave) {
		this.validateTestExecutionBeforeSave = validateTestExecutionBeforeSave;
	}
	
	public void setConfiguration(Configuration configuration) {
		this.configuration = configuration;
	}
	
	public void setTestcaseDefinitionDao(TestcaseDefinitionDao testcaseDefinitionDao) {
		this.testcaseDefinitionDao = testcaseDefinitionDao;
	}

	public void setTestExecutionManipulationService(TestExecutionManipulationService testExecutionManipulationService) {
		this.testExecutionManipulationService = testExecutionManipulationService;
	}
	
	public void setUnmarshallerFascade(JaxbUnmarshallerFascade unmarshallerFascade) {
		this.unmarshallerFascade = unmarshallerFascade;
	}

	public void setTestExecutionDao(TestExecutionDao testExecutionDao) {
		this.testExecutionDao = testExecutionDao;
	}

	@Override
	public void updateStatus(String testcaseExecutionId, String newResolutionStatus, String remark)
			throws TcmdbRecordNotFoundException, TcmdbValidationFailedException {
		
		if (log.isDebugEnabled()) {
			log.debug("updateStatus: >> " + testcaseExecutionId + ", " + newResolutionStatus + ", " + remark);
		}
		
		WsTestExecution execution;
		try {
			execution = testExecutionDao.load(testcaseExecutionId);
		}
		catch(DataRetrievalFailureException e) {
			throw new TcmdbRecordNotFoundException("ID=" + testcaseExecutionId + ", " + e.getMessage());
		}
		
		if(!StringUtils.isBlank(remark)) {
			execution.setExecutionRemark(remark);
		}
		
		execution.setResolution(newResolutionStatus);
		
		validateTestExecutionBeforeSave.validate(execution);		
		testExecutionDao.saveReplace(execution);
		
		if (log.isDebugEnabled()) {
			log.debug("updateStatus: <<");
		}
		
	}

	@Override
	public void saveNewByTestcaseDefinition(String testcaseDefinitionId, InputStream bodyData)
			throws TcmdbRecordNotFoundException, TcmdbValidationFailedException {
		if (log.isDebugEnabled()) {
			log.debug("saveNewByTestcaseDefinition: >> " + testcaseDefinitionId + ", " + bodyData);
		}

		WsTestExecution emptyExecution = buildExecutionByDefinition(testcaseDefinitionId);		
		applyOptionalTemplateData(bodyData, emptyExecution);
		
		testExecutionDao.saveNew(emptyExecution);

		if (log.isDebugEnabled()) {
			log.debug("saveNewByTestcaseDefinition: << " + emptyExecution);
		}

		WebserviceTemplate template = new WebserviceTemplateFactory().build();
		template.getWebserviceResponse().setBody(emptyExecution);
	}

	private void applyOptionalTemplateData(InputStream bodyData, WsTestExecution emptyExecution) {
		WsTestExecution execution = toWsTestExecution(bodyData);
		if (execution != null) {
			RolloutDefinitionByExistingExecutionStrategy strategy = new RolloutDefinitionByExistingExecutionStrategy();
			strategy.setDefaultResolution(TestExecutionUtils.getDefaultResolution(configuration));
			
			testExecutionManipulationService.copy(execution, emptyExecution, strategy);
		}
	}

	private WsTestExecution buildExecutionByDefinition(String testcaseDefinitionId) throws TcmdbRecordNotFoundException {
		WsTestcaseDefinition definition;
		try {
			definition = testcaseDefinitionDao.load(testcaseDefinitionId);
		}
		catch (DataRetrievalFailureException e) {
			throw new TcmdbRecordNotFoundException(e);
		}
		WsTestExecution execution = testExecutionManipulationService.buildInstanceBy(definition);
		return execution;
	}

	private WsTestExecution toWsTestExecution(InputStream bodyData) {
		if (log.isDebugEnabled()) {
			log.debug("toWsExecution: >> " + bodyData);
		}

		WsTestExecution execution = null;
		if (bodyData != null) {
			execution = unmarshallerFascade.unmarshall(bodyData, WsTestExecution.class);
		}

		if (log.isDebugEnabled()) {
			log.debug("toWsExecution: << " + execution);
		}
		return execution;
	}

	@Override
	public void rolloutMultipleExecutionsByExisting(String testcaseExecutionId, InputStream bodyData)
			throws TcmdbRecordNotFoundException {
		if (log.isDebugEnabled()) {
			log.debug("rolloutMultipleExecutionsByExisting: >> " + testcaseExecutionId + ", " + bodyData);
		} 
		
		Assert.notNull(bodyData, "bodyData may not be null");
		
		String[] testcaseDefinitionIds = getTestcaseDefinitionIds(bodyData); 		
		List<WsTestcaseDefinition> definitions = assertTestcaseDefinitionsAllExist(testcaseDefinitionIds);
		
		List<String> createdExecutionIds = createCopiesOfTestExecution(testcaseExecutionId, definitions);
		WsListResult<String> result = new WsListResult<String>(createdExecutionIds);
		
		if (log.isDebugEnabled()) {
			log.debug("rolloutMultipleExecutionsByExisting: << " + result);
		}
		
		WebserviceTemplate template = new WebserviceTemplateFactory().build();
		template.getWebserviceResponse().setBody(result);
	}

	private List<String> createCopiesOfTestExecution(String testcaseExecutionId, List<WsTestcaseDefinition> definitions) {
		RolloutDefinitionByExistingExecutionStrategy strategy = new RolloutDefinitionByExistingExecutionStrategy();
		strategy.setDefaultResolution(TestExecutionUtils.getDefaultResolution(configuration));
		
		WsTestExecution executionTemplate = testExecutionDao.load(testcaseExecutionId);
		List<String> createdExecutionIds = new LinkedList<String>();
		
		for(WsTestcaseDefinition definition : definitions) {
			WsTestExecution newExecutionInstance = testExecutionManipulationService.buildInstanceBy(definition);
			testExecutionManipulationService.copy(
					executionTemplate, 
					newExecutionInstance, 
					strategy
				);
			
			newExecutionInstance = testExecutionDao.saveNew(newExecutionInstance);			
			assert newExecutionInstance.getId() != null;		
			
			createdExecutionIds.add(newExecutionInstance.getId());
		}
		
		return createdExecutionIds;
	}

	private String[] getTestcaseDefinitionIds(InputStream bodyData) {
		String[] testcaseDefinitionIds;
		try {
			testcaseDefinitionIds = StringUtils.split(IOUtils.toString(bodyData), ",; ");			
		}
		catch (IOException e) {
			throw new IllegalStateException(e);
		}
		return testcaseDefinitionIds;
	}

	private List<WsTestcaseDefinition> assertTestcaseDefinitionsAllExist(String[] testcaseDefinitionIds) {
		List<WsTestcaseDefinition> definitions = new LinkedList<WsTestcaseDefinition>();
		List<String> missingDefinitionIds = new LinkedList<String>();
		
		for(String definitionId : testcaseDefinitionIds) {
			WsTestcaseDefinition definition = testcaseDefinitionDao.get(definitionId);
			if(definition == null) {
				missingDefinitionIds.add(definitionId);
			}
			else {
				definitions.add(definition);
			}
		}
	
		if(!missingDefinitionIds.isEmpty()) {
			throw new TcmdbRecordNotFoundException("not all requested definitions are known: " + missingDefinitionIds);
		}
		return definitions;
	}

	@Override
	public void getAllActiveGroups() {
		if (log.isDebugEnabled()) {
			log.debug("getAllActiveGroups: >> ");
		}

		Collection<String> dbResult = testExecutionDao.findGroupedValues("group");

		WsListResult<String> wsResult = new RestTransformerSupport().toWsListResult(
				dbResult, new SameInstanceTransformer<String>());

		if (log.isDebugEnabled()) {
			log.debug("getAllActiveGroups: << " + wsResult);
		}
		WebserviceTemplate template = new WebserviceTemplateFactory().build();
		template.getWebserviceResponse().setBody(wsResult);
	}

	@Override
	public void getExecutionDocumentsByGroup(String group) {
		if (log.isDebugEnabled()) {
			log.debug("getExecutionDocumentsByGroup: >> " + group);
		}

		Criteria criteria = new Criteria("group").is(group);
		List<WsTestExecution> dbResult = testExecutionDao.findByCriteria(criteria);

		WsListResult<WsTestExecution> wsResult = new RestTransformerSupport().toWsListResult(
				dbResult, new SameInstanceTransformer<WsTestExecution>());

		if (log.isDebugEnabled()) {
			log.debug("getExecutionDocumentsByGroup: << " + wsResult);
		}
		WebserviceTemplate template = new WebserviceTemplateFactory().build();
		template.getWebserviceResponse().setBody(wsResult);
	}

	@Override
	public void saveExistingExecution(String testcaseExecutionId, InputStream bodyData) throws TcmdbRecordNotFoundException,
			TcmdbValidationFailedException {
		if (log.isDebugEnabled()) {
			log.debug("saveExistingExecution: >> + " + testcaseExecutionId + ", " + bodyData);
		}
		
		loadExecutionAndAssertExist(testcaseExecutionId);
		unmarshallAndSaveExisting(testcaseExecutionId, bodyData);		
		WsTestExecution result = loadExecutionAndAssertExist(testcaseExecutionId);
		
		if (log.isDebugEnabled()) {
			log.debug("saveExistingExecution: << " + result);
		}
		WebserviceTemplate template = new WebserviceTemplateFactory().build();
		template.getWebserviceResponse().setBody(result);
	}

	private void unmarshallAndSaveExisting(String testcaseExecutionId, InputStream bodyData) {
		WsTestExecution result;
		result = unmarshallerFascade.unmarshall(bodyData, WsTestExecution.class);
		result.setId(testcaseExecutionId);
		testExecutionDao.saveReplace(result);
	}

	private WsTestExecution loadExecutionAndAssertExist(String testcaseExecutionId) {
		WsTestExecution result = null;
		try {
			result = testExecutionDao.load(testcaseExecutionId);
		}
		catch (DataRetrievalFailureException e) {
			throw new TcmdbRecordNotFoundException(e);
		}
		return result;
	}

}
